package legoserver;
/**
 *
 * @author bjorn
 * @coauthor Daniel Van Eck
 * 
 */

import ServerClient.*;
//import java.util.logging.Level;
//import java.util.logging.Logger;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.awt.image.*;
import javax.imageio.*;
import java.util.*;

public class ClientConnection extends Thread {

private TcpConnection conn;
private ClientServerCmd command;
private VideoTransmit3 videoServer;
private ServerGUI gui;

private boolean acceptingCmds = false;

public static final int PORT = 9070;
public static final long ROBOT_SEND_CMD_TIMEOUT = 20000;
public static final long CLIENT_CONNECT_TIMEOUT = 30000;
public static final long PATHFINDER_TIMEOUT = 3000;
public static final String VIDEO_TRANSMIT_PORT = "9079";
public static final String CAMERA_ID = "vfw://0";

public ClientConnection(ServerGUI gui) {
    
    this.gui = gui;
    conn = new TcpConnection(PORT);
    acceptingCmds = true;
    this.start();
}//end-ClientConnection

public VideoTransmit3 getVideoServer() {
    return videoServer;
}

public void run() {
    update("Waiting for a client...");
    
    while(!waitForClientConn(CLIENT_CONNECT_TIMEOUT)) {
        //keep on waiting
    }//end-while
    
    update("Client connected.");
    
    update("Beginning transmission of video data...");
    videoServer = new VideoTransmit3(CAMERA_ID, conn.getClientIp().getHostAddress(),
                                        VIDEO_TRANSMIT_PORT, gui);
    videoServer.start();
    
    update("Awaiting commands...");
    while (acceptingCmds) {
         try { // Poll every ~10 ms
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            //do nothing, it doesn't matter if we get interrupted while sleeping
        }
        command = (ClientServerCmd) conn.readCmd();

        if (command != null) {
            /*if(!waitForRobotConn(ROBOT_SEND_CMD_TIMEOUT)) {
                update("Timed-out sending command " + command.getCommand() +
                       " to robot connection.");
            }
            else {*/
                switch (command.getCommand()) {
                    case ClientServerCmd.STOP:
                        if(!sendRobotCmd(RobotConnection.STOP))
                            update("Failed to send cmd " + command.getCommand());
                        break;
                    // Stop the robot
                    case ClientServerCmd.MOVE_FORWARD:
                        if(!sendRobotCmd(RobotConnection.MOVE_FORWARD))
                            update("Failed to send cmd " + command.getCommand());
                        break;

                    case ClientServerCmd.MOVE_BACKWARD:
                        if(!sendRobotCmd(RobotConnection.MOVE_BACKWARD))
                            update("Failed to send cmd " + command.getCommand());
                        break;

                    case ClientServerCmd.TURN_LEFT:
                        if(!sendRobotCmd(RobotConnection.TURN_LEFT))
                            update("Failed to send cmd " + command.getCommand());
                        break;

                    case ClientServerCmd.TURN_RIGHT:
                        if(!sendRobotCmd(RobotConnection.TURN_RIGHT))
                            update("Failed to send cmd " + command.getCommand());
                        break;

                    case ClientServerCmd.GOTO:
                        update("DEBUG: GOTO running... ");

                        //Get start and stop coordinates.
                        Point start, stop;
                        start = command.getStartCoord();
                        stop = command.getStopCoord();

                        update("Recieved strt coords: x=" + start.x + " | y=" + start.y);
                        update("Recieved dest coords: x=" + stop.x + " | y=" + stop.y);

                        //update("(DEBUG)Getting Image...");
                        Image snapshot = command.getSnapshot();
                        //update("(DEBUG)Got Image.");
                        
                 /*      update("Width of image=" + snapshot.getWidth(null));
                        update("Height of image=" + snapshot.getHeight(null));*/
                        
                        /*Image snapshot = null;
                        try {
                            snapshot = videoServer.grabFrame();
                        }catch(IOException e) {
                            update("An exception occurred while grabbing frame.");
                        }

                        if(snapshot == null) {
                            update("Failed to grab frame from webcam.");
                            break;
                        }
                        
                        BufferedImage buffImg = new BufferedImage(snapshot.getWidth(null), snapshot.getHeight(null), BufferedImage.TYPE_INT_RGB);
                        try {
                            ImageIO.write(buffImg, "jpg", new File("c:\\from_goto.jpg"));
                        }catch(IOException e) {
                            update("FAILED TO WRITE IMAGE TO DISK!!!");
                        }*/
                        
                        //testing code
                        //BufferedImage buffImg = new BufferedImage(snapshot.getWidth(null), snapshot.getHeight(null), BufferedImage.TYPE_INT_RGB);
                        
//                        BufferedImage buffImg = new BufferedImage
                        /*BufferedImage buffImg = getBufferedImage(snapshot);
                        
                        try {
                            ImageIO.write(buffImg, "jpg", new File("c:\\webcam.jpg"));
                        }catch(IOException exc) {
                            update("EXCEPTION WRITING TEST IMAGE TO DISK.");
                        }*/

                        //update the server's copy of the image
                        //gui.getLegoMazePathfinder().getVision().refreshImg(snapshot);
                        //not really...
                        
                        //refresh image MUST come before set coords
                        
                        Vector path;
                        gui.getLegoMazePathfinder().getPath(snapshot, start, stop);
                        
                        //wait for the pathfinder to be ready
                        /*if(waitForPathFinder(PATHFINDER_TIMEOUT)){
                            update("Timed out waiting for pathfinder.");
                            update("Aborting navigate...");
                            break;
                        }*/
                        try {
                            Thread.sleep(2000);
                        }catch(InterruptedException e) {
                            //do nothing keep going
                        }
                        
                        path = gui.getLegoMazePathfinder().getSolution();
                        
                        Point curPoint;
                        for(int n = 0; n < path.size(); n++) {
                            curPoint = (Point)path.elementAt(n);
                            update("path.elemantAt(" + n + ")=" + curPoint.x + "," + curPoint.y);
                        }
                        
                        if( (path == null) || (path.size() <= 0) ){
                            update("Failed to find a path.");
                            break;
                        }
                        
                        //testing
                        //gui.getLegoMazePathfinder().printMap();
                        
                        //now notify robotconnection of new path
                        if( waitForRobotConn(ROBOT_SEND_CMD_TIMEOUT) ) {
                            gui.getRobotConnection().navigateToPoint(path);
                        }
                        else {
                            update("Timed-out sending navigate cmd.");
                        }

                        break;

                    case ClientServerCmd.NULL:
                        //do nothing
                        break;
                    default:
                        update("Invalid command received from client: \"" +
                                command.getCommand() + "\"");
                }//end-switch
            //}//end-else
        }//end-if
    }//end-while
}//end-run


/**
 *
 * @param ms  Wait this many milliseconds
 * @return boolean false if the robot connection is still busy, true otherwise
 */
private boolean waitForRobotConn(long ms) {
    long startTime = System.currentTimeMillis();
    long endTime = startTime + ms;

    while(  System.currentTimeMillis() <= endTime &&
            (gui.getRobotConnection() == null)  ) {

        //wait for robot to be initialized if necessary
        try {
            Thread.sleep(1000);
        }catch(InterruptedException e) {
            //do nothing, keep waiting for conn
        }//end-catch
    }

    //if robot connection is still null, return false
    if(gui.getRobotConnection() == null)
        return false;

    while(  gui.getRobotConnection().isBusy() &&
            System.currentTimeMillis() <= endTime  ) {
        //wait for timeout or robot connection to stop being busy
        try {
            Thread.sleep(1000);
        }catch(InterruptedException e) {
            //do nothing, keep waiting for conn
        }//end-catch
    }
    return !(gui.getRobotConnection().isBusy());
}

public boolean isConnNull() {
    return (conn == null);
}

/**
 * makes a BufferedImage from an image
 * @param image
 * @return BufferedImage
 */
public BufferedImage getBufferedImage(Image image) {
    BufferedImage bufferedImage;
    int width = image.getWidth(null);
    int height = image.getHeight(null);
    bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    Graphics2D g = bufferedImage.createGraphics();
    g.drawImage(image, null, null);

    return bufferedImage;
}

/**
 * 
 * @param ms  Wait this many milliseconds
 * @return boolean false if the tcp connection is not ready, true otherwise
 */
private boolean waitForClientConn(long ms) {
    long startTime = System.currentTimeMillis();
    long endTime = startTime + ms;
    
    while(  System.currentTimeMillis() <= endTime &&
            conn == null  ) {
        //wait for connection to get started
        try {
            Thread.sleep(1000);
        }catch(InterruptedException e) {
            //do nothing, keep waiting for conn
        }//end-catch
    }
    
    //if connection is still not made by end of timeout, return false
    if(conn == null) {
        update("Client timeout");
        return false;
        
    }
    //update("waiting for ready");
    while( (!conn.isReady())  &&
           (System.currentTimeMillis() <= endTime) ){
        //wait it out
        try {
            Thread.sleep(1000);
        }catch(InterruptedException e) {
            //do nothing, keep waiting for conn
        }//end-catch
    }
    return conn.isReady();
}

/**
 * 
 * @param ms  Wait this many milliseconds
 * @return boolean false if the tcp connection is not ready, true otherwise
 */
private boolean waitForPathFinder(long ms) {
    long startTime = System.currentTimeMillis();
    long endTime = startTime + ms;
    
    while(  System.currentTimeMillis() <= endTime &&
            gui.getLegoMazePathfinder().isBusy()  ) {
        //wait for connection to get started
        try {
            Thread.sleep(1000);
        }catch(InterruptedException e) {
            //do nothing, keep waiting for conn
        }//end-catch
    }
    
    //if connection is still not made by end of timeout, return false
    if(gui.getLegoMazePathfinder().isBusy()) {
        update("Client timeout");
        return false;
        
    }
    //update("waiting for ready");
    while( (gui.getLegoMazePathfinder().isBusy())  &&
           (System.currentTimeMillis() <= endTime) ){
        //wait it out
        try {
            Thread.sleep(1000);
        }catch(InterruptedException e) {
            //do nothing, keep waiting for conn
        }//end-catch
    }
    return gui.getLegoMazePathfinder().isBusy();
}

/**
 * 
 * @param cmd   - The cmd string to send
 */
private boolean sendRobotCmd(String cmd) {
    RobotConnection r = gui.getRobotConnection();
    if(r == null) {
        update("Unable to forward cmd to robot. Robot thread cannot be found.");
        return false;
    }
    
    try {
        if(!r.sendCmd(cmd)) {
            update("Robot connection failed to send command.");
            return false;
        }
    }catch(IOException e) {
            update("Robot connection failed to send command.");
            return false;
    }
    //Arriving at this point means the sendCmd worked
    return true;
}//end-sendRobotCmd

public void setAcceptingCmds(boolean status) {
    this.acceptingCmds = status;
}

public boolean isAcceptingCmds() {
    return acceptingCmds;
}
    
public void disconnect() {
    setAcceptingCmds(false);
    if(videoServer != null)
        videoServer.killTransmission();
    conn.close();
}

//public VideoTransmit getVideoServer() {
//    if(videoserver.isTransmitting())
//        return videoserver;
//    return null;
//}

public void update(String s) {
    if(gui != null)
        gui.update("ClientConn: " + s);
}
}


